#include <time.h>

#include "armory.h"
#include "consumable.h"
#include "defines.h"
#include "item.h"
#include "manager.h"
#include "maps.h"
#include "monster.h"
#include "tile.h"

using std::list;
using std::vector;
using std::string;

unsigned int MAX_VIEW_RADIUS = 8;

Manager::Manager()
{
	started = false;
	debug = false;
	controlledObject = nullptr;
	player = nullptr;
	curMap = nullptr;
}

Manager::~Manager()
{
	// should probably stay empty
}

void Manager::start()
{
	if( started == true )
		return;
	initializeMap();
	initInterface();
	//player = new Monster( 2, 2, stats_standard, skills_standard, "player", '@' );
	player = instance< Monster >();
	player->setMonster( curMap, 2, 2, stats_standard, skills_standard, "player", '@' );
	addMsg( "Welcome to the Dungeons of Ololollo!" );
	if( prob( 33 ) )
		addMsg( "Nippa~" ); // nippa~
	switch( mrand( 3 ) )
	{
	case 0:
		addMsg( "Your doom awaits you." );
		break;
	case 1:
		addMsg( "Beware of the blacksmith!" );
		break;
	case 2:
		addMsg( "Sponsored by YOUR mom." );
		break;
	}
	controlledObject = player;
	//readLoadMap( map_start );
	loadMap( /*readMap( map_start )*/generateMap() );
	player->setMapPos( curMap, 5, 6 );
	draw(); // make characters appear on the screen before we take the first turn
	updateInterface();
	started = true;
	manager.play();
}

void Manager::play()
{
	int action;
	while( ( action = getch() ) != EXIT_KEY )
	{
		manager.turn( action );
	}
	end();
}

void Manager::turn( int key )
{
	bool cont = false; // if set to false, will not process monsters and shit
	switch( key )
	{
	case KEY_UP:
	case KEY_NUM8:
		cont = keyUp();
		break;
	case KEY_DOWN:
	case KEY_NUM2:
		cont = keyDown();
		break;
	case KEY_LEFT:
	case KEY_NUM4:
		cont = keyLeft();
		break;
	case KEY_RIGHT:
	case KEY_NUM6:
		cont = keyRight();
		break;
	case KEY_NUM9:
		cont = keyNE();
		break;
	case KEY_NUM7:
		cont = keyNW();
		break;
	case KEY_NUM3:
		cont = keySE();
		break;
	case KEY_NUM1:
		cont = keySW();
		break;
	case A_KEY:
		if( debug )
			cont = keyA_DEBUG();
		else
			cont = keyA();
		break;
	case C_KEY:
		int closeDir;
		closeDir = getKeyDir( getch() );
		if( closeDir )
			cont = player->closeDoor( closeDir );
		break;
	case D_KEY:
		if( debug )
			cont = keyD_DEBUG();
		else
			cont = keyD();
		break;
	case F_KEY:
		if( debug )
			keyF();
		else
			cont = startCursor();
		break;
	case G_KEY:
		if( debug )
			cont = keyG_DEBUG();
		else
			cont = keyG();
		break;
	case I_KEY:
		cont = keyI();
		break;
	case M_KEY:
		if( debug )
			cont = keyM_DEBUG();
		else
			cont = false;
		break;
	case O_KEY:
		int openDir;
		openDir = getKeyDir( getch() );
		if( openDir )
			cont = player->openDoor( openDir );
		break;
	case P_KEY:
		cont = playerDrop();
		break;
	case S_KEY:
		if( debug )
			cont = keyS_DEBUG();
		else
			cont = keyS();
		break;
	case CTRL_D:
		cont = keyCTRL_D();
		break;
	case DOT_KEY:
		cont = true;
		break;
	case COMMA_KEY:
		cont = playerPickUp();
		break;
	case KEY_F( 1 ):
	case QUESTION_KEY:
		if( debug )
			showWeaponTypes();
		else
			openHelp();
		break;
	case KEY_F( 5 ):
		debug = !debug;
		cont = false;
		break;
	case KEY_PLUS:
		MAX_VIEW_RADIUS++;
		MAX_VIEW_RADIUS = MAX_VIEW_RADIUS > 20 ? 20 : MAX_VIEW_RADIUS;
		break;
	case KEY_MINUS:
		MAX_VIEW_RADIUS--;
		MAX_VIEW_RADIUS = MAX_VIEW_RADIUS > 20 ? 0 : MAX_VIEW_RADIUS;
		break;
	case KEY_F( 2 ):
		listMapObjects();
		break;
	case ENTER_KEY:
		cont = playerShoot();
		break;
	case H_KEY:
		if( debug )
			spawnItem();
		break;
	case KEY_STDOWN:
		loadMap( tileAt( player )->stairTo );
		break;
	default:
		cont = false;
		return;
	}
	if( !waiting && cont )
	{
		processMonsters();
	}
	draw();
	updateInterface();
}

int Manager::mapaddch( int y, int x, int symbol )
{
	return mvwaddch( mapscr, y, x, symbol );
}

void Manager::draw( int fov )
{
	if( fov ) // do we want to recalculate it?
		calculateFOV( player );
		/*calculateShadows( player );*/
	for( unsigned int i = 0; i < MAP_X; i++ )
		for( unsigned int j = 0; j < MAP_Y; j++ )
			tileAt( i, j )->draw();
	if( !tileAt( controlledObject )->visible )
		controlledObject->draw();

	redrawwin( mapscr );
	wrefresh( mapscr );
}

void Manager::calculateFOV( const MapObject * center )
{
	for( int i = 0; i < MAP_X; i++ )
		for( int j = 0; j < MAP_Y; j++ )
			tileAt( i, j )->visible = false; // resetting the tiles' visibility

	list< Tile * > radTiles = getTilesRad( center, MAX_VIEW_RADIUS );
	for( auto iter = radTiles.begin();
		iter != radTiles.end(); iter++ )
	{
		int x0 = player->getX();
		int y0 = player->getY();
		int x1 = ( *iter )->getX();
		int y1 = ( *iter )->getY();
		int dX = x1 - x0;
		int dY = y1 - y0;
		if( dX < 0 )
			dX = -dX;
		if( dY < 0 )
			dY = -dY;
		int sX = 1;
		int sY = 1;
		if( x0 > x1 )
			sX = -1;
		if( y0 > y1 )
			sY = -1;
		int err = dX - dY;
		while( 1 )
		{
			Tile * curTile = tileAt( x0, y0 );
			if( !curTile->visible )
				curTile->visible = true;
			if( !curTile->passable )
				break;
			if( x0 == x1 && y0 == y1 )
				break;
			int e2 = err * 2;
			if( e2 > -dY )
			{
				err -= dY;
				x0 += sX;
			}
			if( e2 < dX )
			{
				err += dX;
				y0 += sY;
			}
		}
	}
}

void Manager::calculateShadows( const MapObject * center )
{	
	for( int i = 0; i < MAP_X; i++ )
		for( int j = 0; j < MAP_Y; j++ )
			tileAt( i, j )->visible = false;

	const int ANGS = 100;

	struct
	{
		double sa;
		double ea;
	} angles[ ANGS ] = { 0 };

	Tile * t = tileAt( center );
	if( !t )
		return;

	for( int y = 0; center->getY() + y >= 0 && center->getY() + y < MAP_Y; y++ )
	{
		for( int x = 0; center->getX() + x >= 0
			&& center->getX() + x < MAP_X && x <= y; x++ )
		{
			double range = 1.0 / ( y + 1 );
			double sa = range * x;
			double ma = sa + range / 2;
			double ea = sa + range;
			Tile * curTile = tileAt( x + center->getX(), y + center->getY() );
			bool seen = true;
			int i;
			for( i = 0; i < ANGS; i++ )
			{
				bool blockeds = false;
				bool blockedm = false;
				bool blockede = false;
				if( !angles[ i ].ea )
					break;
				if( angles[ i ].sa <= sa && angles[ i ].ea >= sa )
					blockeds = true;
				if( angles[ i ].sa <= ma && angles[ i ].ea >= ma )
					blockedm = true;
				if( angles[ i ].sa <= ea && angles[ i ].ea >= ea )
					blockede = true;

				if( !curTile->passable & 0 )
				{
					if( blockeds + blockede + blockedm > 2 )
					{
						curTile->visible = false;
						seen = false;
						break;
					}
				}
				else
				{
					if( ( blockedm || ( blockeds && blockede ) ) )
					{
						curTile->visible = false;
						seen = false;
						break;
					}
				}
			}
			if( seen )
				curTile->visible = true;
			if( !curTile->passable && seen )
			{
				if( angles[ i ].ea )
					angles[ ++i ].sa = sa;
				else
					angles[ i ].sa = sa;
				angles[ i ].ea = ea;
			}
		}
	}
	for( int i = 0; i < ANGS; i++ )
		angles[ i ].sa = 0, angles[ i ].ea = 0;

	for( int x = 0; center->getX() + x >= 0	&& center->getX() + x < MAP_X; x++ )
	{
		for( int y = 0; center->getY() + y >= 0 && center->getY() + y < MAP_Y && y <= x; y++ )
		{
			double range = 1.0 / ( x + 1 );
			double sa = range * y;
			double ma = sa + range / 2;
			double ea = sa + range;
			Tile * curTile = tileAt( x + center->getX(), y + center->getY() );
			bool seen = true;
			int i;
			for( i = 0; i < ANGS; i++ )
			{
				bool blockeds = false;
				bool blockedm = false;
				bool blockede = false;
				if( !angles[ i ].ea )
					break;
				if( angles[ i ].sa <= sa && angles[ i ].ea >= sa )
					blockeds = true;
				if( angles[ i ].sa <= ma && angles[ i ].ea >= ma )
					blockedm = true;
				if( angles[ i ].sa <= ea && angles[ i ].ea >= ea )
					blockede = true;
				if( angles[ i ].sa <= sa && angles[ i ].ea >= ea )
					blockeds = blockedm = blockede = true;

				if( !curTile->passable & 0 )
				{
					if( blockeds + blockede + blockedm > 2 )
					{
						curTile->visible = false;
						seen = false;
						break;
					}
				}
				else
				{
					if( ( blockedm || ( blockeds && blockede ) ) )
					{
						curTile->visible = false;
						seen = false;
						break;
					}
				}
			}
			if( seen )
				curTile->visible = true;
			if( !curTile->passable && seen )
			{
				if( angles[ i ].ea )
					angles[ ++i ].sa = sa;
				else
					angles[ i ].sa = sa;
				angles[ i ].ea = ea;
			}
		}
	}
}

/*
To do: code monster picking up and using items.
*/
void Manager::processMonsters()
{
	auto monsterIter = curMap->mapMonsters.begin();
	for( monsterIter; monsterIter != curMap->mapMonsters.end(); monsterIter++ )
	{
		Monster * mon = *monsterIter;
		if( mon == player || mon->isDead() )
			continue;
		if( !tileAt( mon )->visible )
		{
			if( prob( 10 ) )
				mon->move( mrand( 8 ) + 1 );
		}
		else if( prob( 80 ) )
			mon->moveAttack( getDir( mon, player ) );
	}
}

bool Manager::wait()
{
	if( waiting == false )
	{
		waiting = true;
		return true;
	}
	else
		return false;
}

bool Manager::resume()
{
	if( waiting == true )
	{
		waiting = false;
		return true;
	}
	else
		return false;
}

/*
###             ###
### MAP RELATED ###
###             ###
*/

void Manager::initializeMap()
{
	mapscr = newScreen( 20, 80, 2, 0 );
	scrollok( mapscr, FALSE ); // scrolling a map is not ok
	for( int i = 0; i < 2; i++ )
	{
		map * m = new map;
		maps.push_back( m );
		curMap = ( maps.back() );
		m->mapIndex = i + 1; // 1 to 4
		for( int k = 0; k < MAP_X; k++ )
			for( int l = 0; l < MAP_Y; l++ )
			{
				m->mapTiles[ k ][ l ] = nullptr;
				Tile * newTile = new Tile( k, l );
				m->mapTiles[ k ][ l ] = newTile;
			}
	}
}

void Manager::loadMap( int mapIndex )
{
	for( auto iter = maps.begin(); iter != maps.end(); iter++ )
	{
		map * m = *iter;
		if( m->mapIndex == mapIndex )
		{
			loadMap( m );
			break;
		}
	}
}

void Manager::loadMap( map * mapFile )
{
	// needs its own function transfer_player
	// to teleport him and all his possessions
	curMap = mapFile;
	player->setMapPos( mapFile, 2, 2 );
	draw();
}

map * Manager::readMap( const char * mapFile )
{
	map * m = new map;
	m->mapIndex = maps.size() + 1;
	maps.push_back( m );
	for( int x = 0, y = 0; mapFile && y < MAP_Y;
		mapFile++, x < MAP_X - 1 ? x++ : x = 0, x == 0 ? y++ : 0 )
	{
		m->mapTiles[ x ][ y ] = nullptr;
		Tile * t = instance< Tile >();
		t->setTile( m, x, y );
		switch( *mapFile )
		{
			/*case 'a':
			new armor( x, y );
			while( *mapFile != ',' )
			...*/
		case '.':
			break;
		case '#':
			t->convertToWall();
			break;
		case 'd':
			t->convertToDoor();
			break;/*
		case 'g':
			spawnMonster( goblin, x, y );
			break;
		case 'h':
			spawnConsumable( potionOfHealing, x, y );
			break;
		case 'a':
			spawnArmor( chain_mail, x, y );
			break;*/
		default:
			break;
		}
	}
	return m;
}

void Manager::readLoadMap( const char * mapFile )
{
	map * m = new map;
	m->mapIndex = maps.size() + 1;
	curMap->mapMonsters.remove( player );
	player->setPos( -1, -1 );
	curMap = m;
	maps.push_back( m );
	for( int x = 0, y = 0; mapFile && y < MAP_Y;
		mapFile++, x < MAP_X - 1 ? x++ : x = 0, x == 0 ? y++ : 0 )
	{
		m->mapTiles[ x ][ y ] = nullptr;
		Tile * t;
		t = new Tile( x, y );
		m->mapTiles[ x ][ y ] = t;
		switch( *mapFile )
		{
			/*case 'a':
			new armor( x, y );
			while( *mapFile != ',' )
			...*/
		case '.':
			break;
		case '#':
			t->convertToWall();
			break;
		case 'd':
			t->convertToDoor();
			break;
		case 'g':
			spawnMonster( goblin, x, y );
			break;
		case 'h':
			spawnConsumable( potionOfHealing, x, y );
			break;
		case 'a':
			spawnArmor( chain_mail, x, y );
			break;
		default:
			break;
		}
	}
	m->mapMonsters.push_back( player );
	player->setPos( 2, 2 );
	draw();
}

char ( *mapToStr( char const from[ MAP_X ][ MAP_Y ], char * to ))
{
	char * toInit = to;
	for( int x = 0, y = 0; y < MAP_Y; to++, x++, x >= MAP_X ? (x = 0, y++) : 0 )
	{
		*to = from[ x ][ y ];
	}
	return toInit;
}

char ( *mapTo2D( const char * m, char to[ MAP_X ][ MAP_Y ] ))[ MAP_Y ]
{
	char ( *toInit )[ MAP_Y ] = to;
	for( int i = 0; i < MAP_X * MAP_Y; i++, m++ )
	{
		to[ i % MAP_X ][ i / MAP_X ] = *m;
	}
	return toInit;
}

map * Manager::generateMap()
{
	/*
	generate square as well as elliptic rooms
	get the total area of all rooms, try to generate according shapes, place them randomly, then connect
	different types of dungeons: mazes perhaps? forests, classic dungeons
	*/
	char * mapFile = new char[ MAP_X * MAP_Y ];
	unsigned int xSize = 4;
	unsigned int ySize = 5;
	for( int i = 0; i < MAP_X * MAP_Y; i++ )
	{
		mapFile[ i ] = ( i < MAP_X || i >= ( MAP_Y - 1 ) * MAP_X || i % MAP_X == 0 || ( i > 0 && ( i + 1 ) % MAP_X == 0 ) ) ? '#' : '.';
	}
	for( unsigned int x = 0, y = 0; x < xSize && y < ySize;
		x++, x >= xSize ? x = 0, y++ : 0 )
	{
		mapFile[ MAP_X * ( y + ySize ) + x + xSize ] = '.';
	}
	char map2D[ MAP_X ][ MAP_Y ];
	mapTo2D( mapFile, map2D );
	for( int i = 0, rooms = 0; i < 100 && rooms < 5; i++ )
	{
		unsigned int roomX = 6;
		unsigned int roomY = 5;
		unsigned int cornerX = mrand( MAP_X );
		unsigned int cornerY = mrand( MAP_Y );
		if( cornerX < 1 )
			cornerX = 1;
		else if( cornerX > (MAP_X - roomX - 1) )
			cornerX = MAP_X - roomX - 1;
		if( cornerY < 1 )
			cornerY = 1;
		else if( cornerY > (MAP_Y - roomY - 1) )
			cornerY = MAP_Y - roomY - 1;
		int doorX = cornerX + roomX - 1;
		int doorY = cornerY + roomY/2 - 1;
		bool occupied;
		for( int x = cornerX, y = cornerY;
			x < roomX + cornerX && y < roomY + cornerY;
			x++, x >= (roomX + cornerX) ? ( x = cornerX, y++ ) : 0 )
		{
			if( map2D[ x ][ y ] == '#' )
			{
				occupied = true;
				break;
			}
		}
		if( occupied )
			continue;
		for( int x = cornerX, y = cornerY;
			x < roomX + cornerX && y < roomY + cornerY;
			x++, x >= (roomX + cornerX) ? ( x = cornerX, y++ ) : 0 )
		{
			if( x == cornerX || x == (cornerX + roomX - 1)
				|| y == cornerY || y == (cornerY + roomY - 1) )
				map2D[ x ][ y ] = '#';
			if( x == doorX && y == doorY )
				map2D[ x ][ y ] = 'd';
		}
	}
	
	mapToStr( map2D, mapFile );
	map * m = readMap( mapFile );
	return m;
}

std::list< MapObject * > & Manager::getMObjs()
{
	return curMap->mapObjects;
}

Tile * ( &Manager::getMTiles() )[ MAP_X ][ MAP_Y ]
{
	return curMap->mapTiles;
}

std::list< Item * > & Manager::getMItems()
{
	return curMap->mapItems;
}

std::list< Monster * > & Manager::getMMrs()
{
	return curMap->mapMonsters;
}

/*
###                 ###
### EVENTS HANDLERS ###
###                 ###
*/

void Manager::addFlyMonster( Monster * mon, int force, int direction )
{
	flyEvent f;
	f.monster = mon;
	f.force = force;
	f.direction = direction;
	flyingMonsters.push_back( f );
}

void Manager::flyMonsters()
{
	// while there are monsters to fly
	while( flyingMonsters.size() )
	{
		// fly one tile
		for( auto iter = flyingMonsters.begin();
			iter != flyingMonsters.end(); iter++ )
		{
			if( iter->monster->move( iter->direction ) && iter->force )
				--( iter->force );
			else
			{
				addMsg( iter->monster->name + " slams into an obstacle!" );
				iter->monster->harm( 10 );
				iter->force = 0;
			}
		}
		for( auto iter = flyingMonsters.begin();
			iter != flyingMonsters.end(); iter++ )
			if( !iter->force )
				iter = flyingMonsters.erase( iter );
		doDraw();
		wrefresh( mapscr );
		delay( 30 );
	}
}

/*
###           ###
### INTERFACE ###
###           ###
*/

void Manager::initInterface()
{
	msgscr = newScreen( 2, 80, 0, 0 );
	redrawwin( msgscr );
	wrefresh( msgscr );
	statscr = newScreen( 2, 80, 22, 0 );
	redrawwin( statscr );
	wrefresh( statscr );
}

void Manager::updateInterface()
{
	printMessages();
	redrawwin( msgscr );
	wrefresh( msgscr );
	fillWScreen( statscr );
	mvwprintw( statscr, 0, 0, "Health:" );
	wprintw( statscr, "%3d/30", player->getHealth() );
	touchwin( statscr );
	wrefresh( statscr );
	/*mvchgat( 20, 0, -1, A_STANDOUT | A_BOLD, 5, nullptr );//draws the blue^W green^W silver line
	fillScreen( ' ', 21, 0, 25, 80 ); // clear the screen
	Tile * under = tileAt( controlledObject );
	if( under )
	{
		//print the tile's name
		mvprintw( 21, 2, under->name.c_str() );
		//iterate through the contents and list up to three objects 
		auto contentsIter = under->contents.begin();
		for( int i = 0; i < 3 && contentsIter != under->contents.end(); i++, contentsIter++ )
		{
			mvprintw( 22 + i, 2, ( *contentsIter )->name.c_str() );
		}
	}
	mvprintw( 21, 30, "health: %d", player->getHealth() );
	mvprintw( 22, 30, "%d, %d", controlledObject->getX(), controlledObject->getY() );*/
}

void Manager::openHelp()
{
	WINDOW * help = newwin( 25, 80, 0, 0 );
	int action = 0;
	bool browsingHelp = true;
	while( browsingHelp )
	{
		//to-do: add more comprehensive
		//help file
		const char * const text =
			"F5 - debug on/off\n"
			" Debug keys:\n"
			"\ta - spawn armor\n"
			"\td - convert to wall\n"
			"\tg - spawn item\n"
			"\ts - convert to floor\n"
			"\t+/- - change view radius\n"
			" Regular keys:\n"
			"\ta - attack a nearby monster\n"
			"\tf - start/end a cursor\n"
			"\ti - open inventory\n"
			"\tp - drop an item\n"
			"\t, - pick up an item\n"
			"\t. - next turn\n"
			"\t^d - kick\n"
			" Inventory keys:\n"
			"\td - drop an item\n"
			"\tw - wear an armor\n"
			"\tt - take off an armor\n"
			;
		mvwprintw( help, 1, 1, text );
		wrefresh( help );
		action = getch();
		switch( action )
		{
		case ESC_KEY:
			browsingHelp = false;
			break;
		case QUESTION_KEY:
			browsingHelp = false;
			break;
		default:
			break;
		}
	}
	deleteScreen( help );
}

void Manager::openInventory()
{
	WINDOW * inventory = newwin( 25, 80, 0, 0 );//height, width, startY, startX
	wborder( inventory, ':' | COLOR_BLOOD, ':' | COLOR_BLOOD, '.' | COLOR_BLOOD | A_BOLD, '.' | COLOR_BLOOD | A_BOLD, '+', '+', '+', '+' );

	int action = 0;
	bool browsingInventory = true;
	int selected = 1;
	Item * selectedItem = nullptr;
	while( browsingInventory )
	{
		fillWScreen( inventory, ' ', 1, 1, 25 - 1, 80 - 1 );
		auto itemIter = player->items.begin();
		int objCount;
		for( objCount = 0; itemIter != player->items.end() && objCount < 4; itemIter++, objCount++ )
		{
			mvwprintw( inventory, 1 + objCount, 2/*x*/, ( *itemIter )->name.c_str() );
		}
		if( selected > objCount )
			selected = objCount;
		else if( selected < 1 )
			selected = 1;
		if( objCount != 0 )
		{
			mvwchgat( inventory, selected, 2, 80 - 3, A_BOLD, 0/*color*/, NULL );
			auto selectedIter = player->items.begin();
			selectedIter += selected - 1;
			selectedItem = ( *selectedIter );
		}
		else
		{
			mvwprintw( inventory, 2, 1, "Inventory is empty" );
		}
		wrefresh( inventory );
		action = getch();
		switch( action )
		{
		case EXIT_KEY:
			//end gaem
			break;
		case ESC_KEY:
			browsingInventory = false;
			break;
		case I_KEY:
			browsingInventory = false;
			break;
		case KEY_UP:
		case KEY_NUM8:
			--selected;
			break;
		case KEY_DOWN:
		case KEY_NUM2:
			++selected;
			break;
		case D_KEY:
			player->drop( selectedItem );
			browsingInventory = false;
			break;
		case T_KEY:
			player->takeOff( selectedItem );
			browsingInventory = false;
			break;
		case W_KEY:
			player->wear( selectedItem );
			browsingInventory = false;
			break;
		case ENTER_KEY:
			if( !selectedItem )
				break;
			if( isWeapon( selectedItem ) )
			{
				// if the player has a weapon
				if( player->getWeapon() )
				{
					// if the item is the player's weapon
					if( player->getWeapon() == selectedItem )
						player->sheathe( selectedItem );
					else
						addMsg( "You are already wielding a weapon." );
				}
				else
					player->wield( selectedItem );
			}
			else if( isArmor( selectedItem ) )
			{
				if( player->getArmor() )
				{
					if( player->getArmor() == selectedItem )
						player->takeOff( selectedItem );
					else
						addMsg( "You are already wearing armor." );
				}
				else
					player->wear( selectedItem );
			}
			else if( isConsumable( selectedItem ) )
				player->consume( selectedItem );
			else
				addMsg( "You have no idea what to do with your "
				+ selectedItem->name );
			browsingInventory = false;
			break;
		case S_KEY:
			player->sheathe( selectedItem );
			browsingInventory = false;
			break;
		default:
			break;
		}
	}
	deleteScreen( inventory );
	return;
}

void Manager::addMsg( string const & msg )
{
	string * message = new string( msg );
	messages.push_back( message );
}

void Manager::addMsg( const char * msg )
{
	string * message = new string( msg );
	messages.push_back( message );
}

void Manager::addVisMsg( string const & msg, MapObject * mobj )
{
	if( tileAt( mobj )->visible )
		addMsg( msg );
}

void Manager::addVisMsg( const char * msg, MapObject * mobj )
{
	if( tileAt( mobj )->visible )
		addMsg( msg );
}

void Manager::attackMsg( Monster * assailant, Monster * victim, int damage )
{
	Tile * aT = tileAt( assailant );
	Tile * vT = tileAt( victim );
	if( !aT->visible && !vT->visible )
		return;
	if( aT->visible && !vT->visible )
	{
		if( assailant == player )
			addMsg( "You hit something you cannot see!" );
		else
			addMsg( assailant->name + " hits something!" );
		return;
	}
	if( !aT->visible && vT->visible )
	{
		if( victim == player )
			addMsg( "You are hit by something you can't see!" );
		else
			addMsg( victim->name + " is hit by something!" );
		return;
	}
	string * message = new string;( assailant->name + " hits " + victim->name + "!" );
	string result;
	if( damage < 0 )
		result = assailant == manager.player ? ", but miss." : ", but misses.";
	else if( !damage )
		result = assailant == manager.player ? ", but do no damage." : ", but deals no damage.";
	else
		result = "!";
	if( assailant == victim )
		if( assailant == manager.player )
			*message = "You hit yourself" + result;
		else
			*message = assailant->name + " hits himself" + result;
	else
		if( assailant == manager.player )
			*message = "You hit " + victim->name + result;
		else if( victim == manager.player )
			*message = assailant->name + " hits you" + result;
		else
			*message = assailant->name + " hits " + victim->name + result;
	messages.push_back( message );
}

void Manager::dropMsg( Monster * user, Item * item )
{
	string * message = new string;
	if( user == manager.player )
		*message = "You drop the " + item->name + ".";
	else
		*message = user->name + " drops the " + item->name + ".";
	messages.push_back( message );
}

void Manager::pickMsg( Monster * user, Item * item )
{
	string * message = new string;
	if( user == manager.player )
		*message = "You pick up the " + item->name;
	else
		*message = user->name + " picks up the " + item->name + ".";
	messages.push_back( message );
}

void Manager::dieMsg( Monster * monster )
{
	string * message = new string;
	if( monster == manager.player )
		*message = "You die.";
	else
		*message = monster->name + " dies.";
	messages.push_back( message );
}

int wordLen( const char * s )
{
	char ch;
	int cnt = 0;
	while( ( ch = *s ) == ' ' || ch == '\n' || ch == '\t' )
		++cnt, ++s;
	while( ( ch = *s ) != ' ' && ch != '\n'	&& ch != '\t' && ch != '\0' )
		++cnt, ++s;
	return cnt;
}

const char * moremsg = "-->";
const int morelen = 3;

void Manager::printMessages()
{
	// old messages are dimmed
	mvwchgat( msgscr, 0, 0, -1, A_NORMAL, ( short ) COLOR_OWHITE, 0 );
	mvwchgat( msgscr, 1, 0, -1, A_NORMAL, ( short ) COLOR_OWHITE, 0 );
	// determine the number of new messages
	recentMsgs = messages.size() - recentMsgs;
	auto newmsgs = messages.end();
	// get an iterator to the beginning of new messages
	for( int i = recentMsgs; i > 0; i-- )
		--newmsgs;
	// recent messages are printed in bold
	wattron( msgscr, A_BOLD );
	// don't scroll the window if there's no new messages
	if( recentMsgs > 0 )
		wscrl( msgscr, 1 );
	wmove( msgscr, 1, 0 );
	int left = 80; // blank characters left on the curent line
	int lines = 0; // how many lines have we printed already?
	for( int i = 0; newmsgs != messages.end(); newmsgs++, i++ )
	{
		const char * message = ( *newmsgs )->c_str();
		int wordSize; // length of the current word
		// divide a message into words, print them separately
		while( ( wordSize = wordLen( message ) ) != 0 )
		{
			// the word won't fit in a current line
			if( wordSize >= left - morelen - 1 )
			{
				if( lines > 0 )
				{
					wattroff( msgscr, A_BOLD );
					wprintw( msgscr, "%s", moremsg );
					wattron( msgscr, A_BOLD );
					touchwin( msgscr );
					wrefresh( msgscr );
					wgetch( msgscr );
					wmove( msgscr, 1, getcurx( msgscr ) - morelen );
					wclrtoeol( msgscr );
				}
				wscrl( msgscr, 1 );
				wmove( msgscr, 1, 0 );
				while( *message == ' ' || *message == '\t' || *message == '\n' )
					++message; // skip whitespace characters on the new line
				left = 80;
				++lines;
			}
			// the word fits, print it
			else
			{
				wprintw( msgscr, "%.*s", wordSize, message );
				if( wordSize >= left - morelen - 1 )
				{
					if( lines > 0 )
					{
						wattroff( msgscr, A_BOLD );
						wprintw( msgscr, "%s", moremsg );
						wattron( msgscr, A_BOLD );
						touchwin( msgscr );
						wrefresh( msgscr );
						wgetch( msgscr );
						wmove( msgscr, 1, getcurx( msgscr ) - morelen );
						wclrtoeol( msgscr );
					}
					wscrl( msgscr, 1 );
					wmove( msgscr, 1, 0 );
					left = 80;
					message += wordSize;
				}
				else
				{
					left -= wordSize;
					message += wordSize;
				}
			}
		}
		// add some space between messages
		if( left > 0 )
		{
			wprintw( msgscr, " " );
			--left;
		}
	}
	wattroff( msgscr, A_BOLD );
	recentMsgs = messages.size();
	return;
}

template< typename T >
T * Manager::listOptions( list< T * > options, const char * message )
{
	WINDOW * w = newScreen( 25, 80 );
	mvwprintw( w, 0, 0, "%s", message );
	int action;
	bool browsing = true;
	while( browsing )
	{
		int i = 0;
		for( auto iter = options.begin();
			iter != options.end() && i < 10; iter++, i++ )
		{
			mvwprintw( w, i + 2, 1, "%c - %s", 'a' + i, ( *iter )->name.c_str() );
		}
		wrefresh( w );
		action = getch();
		if( action >= 'a' && action <= 'a' + i )
		{
			auto iter = options.begin();
			for( ; action > 'a'; iter++, action-- )
				;
			return *iter;
		}
		else switch( action )
		{
		case ESC_KEY:
			return nullptr;
		default:
			break;
		}
	}
	deleteScreen( w );
	return nullptr;
}

template< typename T >
T * Manager::listOptions( vector< T * > options, const char * message )
{
	WINDOW * w = newScreen( 25, 80 );
	mvwprintw( w, 0, 0, "%s", message );
	int action;
	bool browsing = true;
	while( browsing )
	{
		int i = 0;
		for( auto iter = options.begin();
			iter != options.end() && i < 10; iter++, i++ )
		{
			mvwprintw( w, i + 2, 1, "%c - %s", 'a' + i, ( *iter )->name.c_str() );
		}
		wrefresh( w );
		action = getch();
		if( action >= 'a' && action <= 'a' + i )
		{
			auto iter = options.begin();
			for( ; action > 'a'; iter++, action-- )
				;
			return *iter;
		}
		else switch( action )
		{
		case ESC_KEY:
			return nullptr;
		default:
			break;
		}
	}
	deleteScreen( w );
	return nullptr;
}

/*
###                 ###
### KEY DEFINITIONS ###
###                 ###
*/


//Control keys
bool Manager::keyUp()
{
	if( controlledObject == player )
		return player->moveAttack( DIR_N );
	return controlledObject->move( DIR_N );
}

bool Manager::keyDown()
{
	if( controlledObject == player )
		return player->moveAttack( DIR_S );
	return controlledObject->move( DIR_S );
}

bool Manager::keyLeft()
{
	if( controlledObject == player )
		return player->moveAttack( DIR_W );
	return controlledObject->move( DIR_W );
}

bool Manager::keyRight()
{
	if( controlledObject == player )
		return player->moveAttack( DIR_E );
	return controlledObject->move( DIR_E );
}

bool Manager::keyNW()
{
	if( controlledObject == player )
		return player->moveAttack( DIR_NW );
	return controlledObject->move( DIR_NW );
}

bool Manager::keyNE()
{
	if( controlledObject == player )
		return player->moveAttack( DIR_NE );
	return controlledObject->move( DIR_NE );
}

bool Manager::keySE()
{
	if( controlledObject == player )
		return player->moveAttack( DIR_SE );
	return controlledObject->move( DIR_SE );
}

bool Manager::keySW()
{
	if( controlledObject == player )
		return player->moveAttack( DIR_SW );
	return controlledObject->move( DIR_SW );
}

/*
###              ###
### REGULAR KEYS ###
###              ###
*/

bool Manager::keyA()
{
	int direction = getch();
	if( isMovKey( direction ) )
	{
		direction = getKeyDir( direction );
		Monster * m = getLiveMonster( controlledObject, direction );
		if( m )
			return player->attack( m );
	}
	return false;
}

bool Manager::keyD()
{
	return false;
}

bool Manager::keyF()
{
	//again, this shit should have its own function
	//startCursor() with its own controls and shit
	if( controlledObject != player )
	{
		delete controlledObject;
		controlledObject = player;
		resume();
	}
	else
	{
		wait();
		controlledObject = new MapObject( player->getX(), player->getY(), "cursor", '*' );
		controlledObject->isGhost = true;
		controlledObject->color = COLOR_GREENB;
		controlledObject->layer = 8;
		controlledObject->drawTo = true;
	}
	return false;
}

bool Manager::keyG()
{
	return false;
}

bool Manager::keyI()
{
	openInventory();
	return false;
}

bool Manager::playerPickUp()
{
	Tile * t = tileAt( player );
	if( !t )
		return false; // error
	Item * item = nullptr;
	if( !t->itemContents.size() )
		return false;
	if( t->itemContents.size() == 1 )
		item = *( t->itemContents.begin() );
	else
		item = listOptions( t->itemContents, "Select an item to pick: " );
	if( item )
	{
		player->pickUp( item );
		return true;
	}
	else
		return false;
}

bool Manager::playerDrop()
{
	Tile * under = tileAt( player->getX(), player->getY() );
	auto iter = player->items.begin();
	for( ; iter != player->items.end(); iter++ )
	{
		player->drop( ( *iter ) );
		return true;
	}
	return false;
}

bool Manager::keyS()
{
	return false;
}

bool Manager::keyCTRL_D()
{
	int kickDir = getch();
	if( isMovKey( kickDir ) )
	{
		kickDir = getKeyDir( kickDir );
		Tile * t = tileAt( player->getX() + getXAdj( kickDir ),
			player->getY() + getYAdj( kickDir ) );
		if( !t )
			return false;
		for( auto iter = t->monsterContents.begin();
			iter != t->monsterContents.end(); iter++ )
		{
			if( !( *iter )->isDead() )
			{
				Monster * mon = *iter;
				return player->kick( mon );
			}
		}
		addMsg( "You kick at empty space." );
		return true;
	}
	else
	{
		return false;
	}
}

bool Manager::playerShoot()
{
	if( !player->getWeapon() )
	{
		addMsg( "You have no weapon readied!" );
		return false;
	}
	Monster * m;
	m = getLiveMonster( tileAt( controlledObject ) );
	if( m )
		return player->shoot( m );
	else
		return player->shoot( tileAt( controlledObject ) );
}

bool Manager::startCursor()
{
	bool cont = false;
	bool done = false;
	int action;
	MapObject * cursor = new MapObject( player->getX(), player->getY(), "cursor", '*' );
	cursor->isGhost = true;
	cursor->color = COLOR_GREENB;
	cursor->layer = 8;
//	controlledObject->drawTo = true; // to do: make this optional
	while( !done && !cont && ( action = getch() ) != ESC_KEY
		&& action != F_KEY )
	{
		if( isMovKey( action ) )
			cursor->move( getKeyDir( action ) );
		else switch( action )
		{
		case ENTER_KEY:
			cont = playerShoot();
			done = true;
			break;
		case K_KEY:
			cursorShowDesc( cursor );
			break;
		default:
			break;
		}
		draw( 0 );
	}
	delete cursor;
	return cont;
}

void Manager::cursorShowDesc( MapObject * cursor )
{
	 Tile * t = tileAt( cursor );
	 vector< MapObject * > l;
	 MapObject * mobj;
	 if( t->contents.size() )
	 {
		l = t->contents;
		l.push_back( t );
		mobj = listOptions( l, "Which object's description to show?" );
		if( mobj )
			mobj->showDesc();
	 }
	 else
		 t->showDesc();
}

/*
###            ###
### DEBUG KEYS ###
###            ###
*/

bool Manager::keyA_DEBUG()
{
	Tile * under = tileAt( controlledObject );
	new Armor( chain_mail, controlledObject->getX(), controlledObject->getY() );
	return false;
}

bool Manager::keyD_DEBUG()
{
	Tile * under = tileAt( controlledObject->getX(), controlledObject->getY() );
	under->convertToWall();
	return false;
}

bool Manager::keyG_DEBUG()
{
	new Item( controlledObject->getX(), controlledObject->getY() );
	return false;
}

bool Manager::keyM_DEBUG()
{
	if( prob( 50 ) )
		new Monster( kobold, controlledObject->getX(), controlledObject->getY() );
	else
		new Monster( goblin, controlledObject->getX(), controlledObject->getY() );
	return false;
}

bool Manager::keyS_DEBUG()
{
	Tile * under = tileAt( controlledObject->getX(), controlledObject->getY() );
	under->convertToFloor();
	return false;
}

void Manager::listMapObjects()
{
	int lineCounter = 0;
	WINDOW * w = newScreen( 25, 80 );
	for( auto iter = curMap->mapObjects.begin(); iter != curMap->mapObjects.end(); iter++ )
	{
		MapObject * mobj = *iter;
		if( lineCounter > 24 )
		{
			fillWScreen( w );
			wmove( w, 0, 0 );
			lineCounter = 0;
			getch();
			wrefresh( w );
		}
		wattron( w, mobj->color );
		wprintw( w, "%s\t%d:%d\n", mobj->name.c_str(), mobj->getX(), mobj->getY() );
		wattroff( w, mobj->color );
		wrefresh( w );
		++lineCounter;
	}
	if( lineCounter > 24 )
	{
		fillWScreen( w );
		wmove( w, 0, 0 );
		wrefresh( w );
	}
	wprintw( w, "--end" );
	wrefresh( w );
	getch();
	deleteScreen( w );
}

void Manager::end()
{
	while( curMap->mapObjects.size() )
		delete *( curMap->mapObjects.begin() );
}

void Manager::testMessages()
{
	for( int i = 0; i < 20; i++ )
	{
		string msg;
		msg = "testmsg number ";
		msg.push_back( i % 10 + '0' );
		addMsg( msg );
	}
}

void Manager::showWeaponTypes()
{
	WINDOW * w = newScreen();
	for( auto iter = getWeaponTypes().begin();
		iter != getWeaponTypes().end(); iter++ )
	{
		wprintw( w, "%s\t%X\n", iter->first.c_str(), iter->second );
	}
	wrefresh( w );
	wgetch( w );
	deleteScreen( w );
}

void Manager::spawnItem()
{
	WINDOW * w = newScreen( 25, 80 );
	wprintw( w,
		"a - item\n"
		"b - armor\n"
		"c - berdanka\n"
		"d - stair\n"
		"e - potion of healing\n" );
	touchwin( w );
	wrefresh( w );
	int action = wgetch( w );
	switch( action )
	{
	case 'a':
		new Item( controlledObject->getX(), controlledObject->getY() );
		break;
	case 'b':
		spawnArmor( chain_mail, controlledObject );
		break;
	case 'c':
		spawnWeapon( berdanka, controlledObject );
		break;
	case 'd':
		tileAt( controlledObject )->convertToStairs( mrand( 3 ) + 1 );
		break;
	case 'e':
		spawnConsumable( potionOfHealing, controlledObject );
		break;
	default:
		break;
	}
	deleteScreen( w );
}